﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace StorageSystem.HT
{
    public sealed class BinaryHsahTable32 : IDisposable, IBinaryHashTable
    {

        #region Variables

        private FileHeader mInfo;
        private string mFileName;
        private bool mHighReliable;

        private StreamRW indexFS;
        private StreamRW dataFS;
        private BinaryFormatter mSerialize;

        // Index type
        private uint[] mLocationIndex;
        private List<uint> mToWrite;


        private const int mFlushIndex = 4000;

        #endregion

        /*//////////////////////////////////////////////////////////////////////////////////////////////*/

        public BinaryHsahTable32(string fileName): this(fileName, 1048576, true)
        {

        }

        /// <summary>
        /// Initialize new instance of DiskBased HashTable 
        /// </summary>
        /// <param name="fileName">The path of the Inverted Index File (IIF)</param>
        public BinaryHsahTable32(string fileName, int capacity, bool highReliable)
        {
            mSerialize = new BinaryFormatter();
            mHighReliable = highReliable;
            mFileName = fileName;
            if (!highReliable) mToWrite = new List<uint>(mFlushIndex);

            dataFS = new StreamRW(fileName + ".HTD", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 4095);
            indexFS = new StreamRW(fileName + ".HTI", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 4095);

            if (indexFS.Length < 1)
            {
                mInfo = new FileHeader() { Capacity = capacity, Count = 0, MaxRound = 0 };
                indexFS.WriteFileHeader(mInfo);
                FirstInit();
            }
            else
            {
                mInfo = indexFS.ReadFileHeader();
                SecondInit();
            }
        }

        /// <summary>
        /// Initizlize the Simple index 'Location index'
        /// </summary>
        private void SecondInit()
        {
            mLocationIndex = new uint[mInfo.Capacity];
            byte[] Bs = new byte[mInfo.Capacity * 4];
            indexFS.Read(Bs, 0, Bs.Length);

            int o = 0;
            for (int i = 0; i < mLocationIndex.Length; i++, o += 4)
                mLocationIndex[i] = StreamRW.ReadUInt32(Bs, o);
        }

        /// <summary>
        /// Initizlize the Simple index 'Location index'
        /// </summary>
        private void FirstInit()
        {
            mLocationIndex = new uint[mInfo.Capacity];

            for (int i = 0; i < mInfo.Capacity; i++)
            {
                mLocationIndex[i] = uint.MaxValue;
                indexFS.WriteUint32(uint.MaxValue);
            }
        }

        /*//////////////////////////////////////////////////////////////////////////////////////////////*/

        /// <summary>
        /// Try get value if it exist
        /// </summary>
        /// <param name="key">the value key</param>
        /// <param name="value">the value</param>
        /// <returns>True if it's exist, False if it not exist</returns>
        public bool TryGetValue(string key, out object value)
        {
            int index = (key.GetHashCode() & 0x7fffffff) % this.mInfo.Capacity;
            BinaryEntry tmp = null;

            tmp = dataFS.SecureReadBinaryItem(mLocationIndex[index], key);

            if (tmp != null)
            {
                value = Deserialize(tmp.Value);
                return true;
            }
            else
            {
                value = null;
                return false;
            }
        }
        
        /// <summary>
        /// Get value for given key
        /// </summary>
        /// <param name="key">The value key</param>
        /// <returns>Return null if data not exists</returns>
        public object GetObjectValue(string key)
        {
            int index = (key.GetHashCode() & 0x7fffffff) % this.mInfo.Capacity;
            BinaryEntry tmp = null;

            tmp = dataFS.SecureReadBinaryItem(mLocationIndex[index], key);

            if (tmp != null)
                return Deserialize(tmp.Value);
            else
                return null;
        }

        /// <summary>
        /// Get value for given key
        /// </summary>
        /// <param name="key">The value key</param>
        /// <returns>Return null if data not exists</returns>
        public List<object> GetObjectValues(string key)
        {
            int index = (key.GetHashCode() & 0x7fffffff) % this.mInfo.Capacity;
            long loc = mLocationIndex[index];
            List<object> R = new List<object>();

            while (true)
            {
                var tmp = dataFS.ReadBinaryItem(loc);

                if (tmp != null)
                {
                    if (tmp.Key == key) R.Add(Deserialize(tmp.Value));
                    if (tmp.NextLocation == -1) return R;
                    loc = tmp.NextLocation;
                }
                else
                    return R;
            }
        }

        /**/

        /// <summary>
        /// Try get value if it exist
        /// </summary>
        /// <param name="key">the value key</param>
        /// <param name="value">the value</param>
        /// <returns>True if it's exist, False if it not exist</returns>
        public bool TryGetValue(string key, out byte[] value)
        {
            int index = (key.GetHashCode() & 0x7fffffff) % this.mInfo.Capacity;
            BinaryEntry tmp = null;

            tmp = dataFS.SecureReadBinaryItem(mLocationIndex[index], key);

            if (tmp != null)
            {
                value = tmp.Value;
                return true;
            }
            else
            {
                value = null;
                return false;
            }
        }

        /// <summary>
        /// Get value for given key
        /// </summary>
        /// <param name="key">The value key</param>
        /// <returns>Return null if data not exists</returns>
        public byte[] GetBinaryValue(string key)
        {
            int index = (key.GetHashCode() & 0x7fffffff) % this.mInfo.Capacity;
            BinaryEntry tmp = null;

            tmp = dataFS.SecureReadBinaryItem(mLocationIndex[index], key);

            if (tmp != null)
                return tmp.Value;
            else
                return null;
        }

        /// <summary>
        /// Get value for given key
        /// </summary>
        /// <param name="key">The value key</param>
        /// <returns>Return null if data not exists</returns>
        public List<byte[]> GetBinaryValues(string key)
        {
            int index = (key.GetHashCode() & 0x7fffffff) % this.mInfo.Capacity;
            long loc = mLocationIndex[index];
            List<byte[]> R = new List<byte[]>();

            while (true)
            {
                var tmp = dataFS.ReadBinaryItem(loc);

                if (tmp != null)
                {
                    if (tmp.Key == key) R.Add(tmp.Value);
                    if (tmp.NextLocation == -1) return R;
                    loc = tmp.NextLocation;
                }
                else
                    return R;
            }
        }



        /// <summary>
        /// Add new elemnt into the HashTable
        /// </summary>
        public void Add(string key, object value)
        {
            int index = (key.GetHashCode() & 0x7fffffff) % this.mInfo.Capacity;
            long loc = mLocationIndex[index];
            ++mInfo.Count;

            if (loc == -1)
            {
                mLocationIndex[index] = (uint)dataFS.Length;

                dataFS.AppendNewItem(key, Serialize(value));

                if (mHighReliable)
                {
                    indexFS.Seek((index * 8) + 12, SeekOrigin.Begin);
                    indexFS.WriteInt64(mLocationIndex[index]);
                }
                else
                {
                    mToWrite.Add((uint)index);
                    if (mToWrite.Count >= mFlushIndex) Flush();
                }
            }
            else
            {
                BinaryEntry E = new BinaryEntry(key, Serialize(value), loc);
                mLocationIndex[index] = (uint)dataFS.Length;

                dataFS.AppendNewItem(E);

                if (mHighReliable)
                {
                    indexFS.Seek((index * 8) + 12, SeekOrigin.Begin);
                    indexFS.WriteInt64(mLocationIndex[index]);
                }
                else
                {
                    mToWrite.Add((uint)index);
                    if (mToWrite.Count >= mFlushIndex) Flush();
                }
            }
        }

        /// <summary>
        /// Add new elment into the HashTable, but check first if it's unique
        /// </summary>
        public bool AddUnique(string key, object value)
        {
            int index = (key.GetHashCode() & 0x7fffffff) % this.mInfo.Capacity;
            long loc = mLocationIndex[index];

            if (loc == -1)
            {
                mLocationIndex[index] = (uint)dataFS.Length;
                dataFS.AppendNewItem(key, Serialize(value));
                
                if (mHighReliable)
                {
                    indexFS.Seek((index * 8) + 12, SeekOrigin.Begin);
                    indexFS.WriteInt64(mLocationIndex[index]);
                }
                else
                {
                    mToWrite.Add((uint)index);
                    if (mToWrite.Count >= mFlushIndex) Flush();
                }
                ++mInfo.Count;
                return true;
            }
            else
            {
                long lastEntry;
                if (!dataFS.GoToLastItemUnique(loc, key, out lastEntry)) return false;

                if (lastEntry == -1) return false;

                dataFS.UpdateNextLocationField(lastEntry + 8);
                dataFS.AppendNewItem(key, Serialize(value));
                ++mInfo.Count;
                return true;
            }
        }

        /**/

        /// <summary>
        /// Add new elemnt into the HashTable
        /// </summary>
        public void Add(string key, byte[] value)
        {
            int index = (key.GetHashCode() & 0x7fffffff) % this.mInfo.Capacity;
            long loc = mLocationIndex[index];
            ++mInfo.Count;

            if (loc == -1)
            {
                mLocationIndex[index] = (uint)dataFS.Length;

                dataFS.AppendNewItem(key, value);

                if (mHighReliable)
                {
                    indexFS.Seek((index * 8) + 12, SeekOrigin.Begin);
                    indexFS.WriteInt64(mLocationIndex[index]);
                }
                else
                {
                    mToWrite.Add((uint)index);
                    if (mToWrite.Count >= mFlushIndex) Flush();
                }
            }
            else
            {
                BinaryEntry E = new BinaryEntry(key, value, loc);
                mLocationIndex[index] = (uint)dataFS.Length;

                dataFS.AppendNewItem(E);

                if (mHighReliable)
                {
                    indexFS.Seek((index * 8) + 12, SeekOrigin.Begin);
                    indexFS.WriteInt64(mLocationIndex[index]);
                }
                else
                {
                    mToWrite.Add((uint)index);
                    if (mToWrite.Count >= mFlushIndex) Flush();
                }
            }
        }

        /// <summary>
        /// Add new elment into the HashTable, but check first if it's unique
        /// </summary>
        public bool AddUnique(string key, byte[] value)
        {
            int index = (key.GetHashCode() & 0x7fffffff) % this.mInfo.Capacity;
            long loc = mLocationIndex[index];

            if (loc == -1)
            {
                mLocationIndex[index] = (uint)dataFS.Length;
                dataFS.AppendNewItem(key, value);

                if (mHighReliable)
                {
                    indexFS.Seek((index * 8) + 12, SeekOrigin.Begin);
                    indexFS.WriteInt64(mLocationIndex[index]);
                }
                else
                {
                    mToWrite.Add((uint)index);
                    if (mToWrite.Count >= mFlushIndex) Flush();
                }
                ++mInfo.Count;
                return true;
            }
            else
            {
                long lastEntry;
                if (!dataFS.GoToLastItemUnique(loc, key, out lastEntry)) return false;

                if (lastEntry == -1) return false;

                dataFS.UpdateNextLocationField(lastEntry + 8);
                dataFS.AppendNewItem(key, value);
                ++mInfo.Count;
                return true;
            }
        }

        private byte[] Serialize(object obj)
        {
            using (MemoryStream mem = new MemoryStream())
            {
                mSerialize.Serialize(mem, obj);

                byte[] Bs = new byte[mem.Length];

                mem.Seek(0, SeekOrigin.Begin);
                mem.Read(Bs, 0, Bs.Length);
                return Bs;
            }
        }

        private object Deserialize(byte[] bytes)
        {
            using (MemoryStream mem = new MemoryStream(bytes))
                return mSerialize.Deserialize(mem);
            
        }


        public void Flush()
        {
            if (mHighReliable) return;
            //mToWrite.Sort();

            foreach (var item in mToWrite)
            {
                indexFS.Seek((item * 8) + 12, SeekOrigin.Begin);
                indexFS.WriteInt64(mLocationIndex[item]);
            }
            mToWrite.Clear();
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (!mHighReliable) Flush();

            indexFS.WriteFileHeader(mInfo);
            indexFS.Close();
            dataFS.Close();
            indexFS.Dispose();
            dataFS.Dispose();
        }

        #endregion
    }
}
